fix(cli): check port availability before starting SSH forward#309
Merged
Conversation
Previously, sandbox create --forward and forward start would attempt the SSH port forward without checking if the port was already in use, resulting in a cryptic 'ssh exited with status 255' error. The sandbox would still be created but the user would not be connected. Add a pre-flight check that tries to bind the port before spawning SSH. If the port is occupied by an existing openshell forward, the error message includes the stop command. If occupied by another process, it suggests lsof to identify it. Applied to both CLI and TUI paths.
Extend --forward to accept [bind_address:]port syntax following SSH -L conventions. For example, --forward 0.0.0.0:8080 binds on all interfaces instead of just localhost. Add ForwardSpec type in openshell-core that handles parsing, SSH -L arg generation, and display formatting. Thread it through the CLI, TUI, and all forwarding call sites. The port availability check also uses the specified bind address.
Instead of falling back to the last-used sandbox when no name is provided, scan the forwards PID directory for the matching port. Ports are unique across sandboxes so the port alone is sufficient to identify which forward to stop.
check_port_available only tested the requested bind address (e.g. 127.0.0.1), so a server listening on [::] (IPv6 wildcard) would not be detected. Now also runs lsof to catch any listener on the port regardless of address family. The error message includes the lsof output and a kill hint so users can free the port.
Move the port availability check to the top of sandbox_create so it runs before the sandbox is provisioned. Previously the check only happened inside sandbox_forward, after the sandbox was already created — leaving an orphaned sandbox if the port was occupied.
Persist the bind address in the forward PID file as a third tab-separated field and display it as a BIND column in 'openshell forward list'. Old PID files without the field default to 127.0.0.1.
pimlock
approved these changes
Mar 15, 2026
…d path The check_port_available call in start_port_forwards ran synchronous I/O (TcpListener::bind and lsof subprocess) inside a tokio::spawn async task, blocking the tokio worker thread and stalling the TUI event loop. This caused the TUI to hang after dropping to the shell for exec commands. The CLI paths (sandbox_create and sandbox_forward) already perform this check in appropriate synchronous contexts, and SSH itself will fail gracefully if the port is unavailable.
drew
added a commit
that referenced
this pull request
Mar 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
openshell forward stopcommandlsof -i :<port> -sTCP:LISTENsandbox create --forward,forward start) and TUI pathsProblem
When running
sandbox create --forward <port>and the port was already in use, the SSH process would fail with a crypticssh exited with status exit status: 255. The sandbox was still created (withkeep=true) but the user was never connected — left with an orphaned sandbox and no actionable guidance.Changes
crates/openshell-core/src/forward.rs— Newcheck_port_available(port)function that attemptsTcpListener::bindand returns actionable error messagescrates/openshell-cli/src/ssh.rs— Callcheck_port_availableat the top ofsandbox_forward(), before SSH session setupcrates/openshell-tui/src/lib.rs— Guard instart_port_forwards()loop that skips unavailable ports with a warningTesting
check_port_available_free_portandcheck_port_available_occupied_portmise run pre-commitpassescargo test -p openshell-core -- forward— all 16 tests pass